#include "lista.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>

/* ******************************************************************
 *                DEFINICION DE LOS TIPOS DE DATOS					*
 * *****************************************************************/

typedef struct nodo {
	void *dato;
	struct nodo *siguiente;
} nodo_t;

struct lista {
	nodo_t *primero;
	nodo_t *ultimo;
	size_t cantidad;
};

struct iterador {
	nodo_t *anterior;
	nodo_t *actual;
};

/* ******************************************************************
 *             IMPLEMENTACION DE PRIMITIVAS DE LA LISTA				*
 * *****************************************************************/
 
// Crea una lista.
// Post: devuelve una nueva lista vacía.
lista_t* lista_crear () {
	lista_t* lista = (lista_t*) malloc(sizeof(lista_t));
	if (lista == NULL) return NULL;
	//Inicializo valores
	lista->primero = NULL;
	lista->ultimo = NULL;
	lista->cantidad = 0;
	return lista;
}

// Devuelve verdadero o falso, según si la lista tiene o no elementos enlazados.
// Pre: la lista fue creada.
bool lista_esta_vacia (const lista_t *lista) {
	return (lista->cantidad == 0);
}

// Destruye la lista. Si se recibe la función destruir_dato por parámetro,
// para cada uno de los elementos de la lista llama a destruir_dato.
// Pre: la lista fue creada. 'destruir_dato' es una función capaz de destruir
// los datos de la cola, o NULL en caso de que no se la utilice.
// Post: se eliminaron todos los elementos de la lista.
void lista_destruir (lista_t *lista, void destruir_dato(void *)) {
	if (!lista) return;
	//Saco los valores y los destruyo si se puede.
	while (!lista_esta_vacia(lista)) {
		void *dato_aux = lista_borrar_primero(lista);
		if (destruir_dato) destruir_dato(dato_aux);
	}
	//Destruyo la lista.
	free (lista);
}

// Agrega un nuevo elemento a la lista. Devuelve falso en caso de error.
// Pre: la lista fue creada.
// Post: se agregó un nuevo elemento al principio o al final de la lista
// según corresponda, 'dato' es dicho elemento.
bool lista_insertar_primero (lista_t *lista,void *dato) {
	//Creo el nodo nuevo.
	nodo_t *nuevo = (nodo_t*) malloc(sizeof(nodo_t));
	if (nuevo == NULL) return false;
	//Le asigno el valor a dato y linkeo el nodo con el siguiente.
	nuevo->dato = dato;
	nuevo->siguiente = lista->primero;
	//Analizo si la lista estaba vacía o no.
	if (lista_esta_vacia(lista)) lista->ultimo = nuevo;
	//Modifico el puntero al primer nodo de la lista.
	lista->primero = nuevo;
	lista->cantidad++;
	return true;
}

bool lista_insertar_ultimo (lista_t *lista,void *dato) {
	//Creo el nodo nuevo.
	nodo_t *nuevo = (nodo_t*) malloc(sizeof(nodo_t));
	if (nuevo == NULL) return false;
	//Le asigno el valor a dato.
	nuevo->dato = dato;
	nuevo->siguiente = NULL;
	//Analizo si la lista estaba vacía o no.
	if (lista_esta_vacia(lista)) {
		lista->primero = nuevo;
	}else{
		lista->ultimo->siguiente = nuevo;
	}
	//Modifico el puntero al último nodo de la lista.
	lista->ultimo = nuevo;
	lista->cantidad++;
	return true;
}

// Obtiene el valor del primer elemento de la lista. Si la lista tiene elementos,
// se devuelve el valor del primer elemento. Si está vacía devuelve NULL.
// Pre: la lista fue creada.
// Post: se devolvió el valor del primer elemento de la lista sin sacarlo
// de ella, cuando ésta no está vacía; NULL en caso contrario.
void* lista_ver_primero (const lista_t *lista) {
	if (lista_esta_vacia(lista)) return NULL;
	return (lista->primero->dato);
}


// Saca el primer elemento de la lista. Si la lista tiene elementos,
// se quita el primero de la lista, y se devuelve ese valor. Si la lista
// está vacía, devuelve NULL.
// Pre: la lista fue creada.
// Post: si la lista no estaba vacía, se devuelve el primer valor de ella
// y la lista contiene un elemento menos.
void* lista_borrar_primero(lista_t *lista) {
	if (lista_esta_vacia(lista)) return NULL;
	nodo_t *aux = lista->primero;
	if (lista->primero == lista->ultimo) lista->ultimo = aux->siguiente;
	lista->primero = aux->siguiente;
	lista->cantidad--;
	void *dat = aux->dato;
	free (aux);
	return dat;
}

// Devuelve el largo de la lista.
// Pre: la lista fue creada.
// Post: se devolvió el largo de la lista. En caso de estar vacía,
// lógicamente, la función devuelve 0 (cero).
size_t lista_largo (const lista_t *lista) {
	return (lista->cantidad);
}


/* ******************************************************************
 *            IMPLEMENTACION DE PRIMITIVAS DEL ITERADOR			    *
 * *****************************************************************/

// Crea un iterador.
// Pre: la lista fue creada.
// Post: se creó un iterador que apunta hacia la primer posición de la lista.
lista_iter_t* lista_iter_crear (const lista_t *lista) {
	lista_iter_t* iter = (lista_iter_t*) malloc(sizeof(lista_iter_t));
	if (iter == NULL) return NULL;
	//Inicializo valores
	iter->anterior = NULL;
	iter->actual = lista->primero;
	return iter;
}

// El iterador avanza en una posición de la lista. Si la lista no tiene
// elementos o el iterador se encuentra al final de ella, devuelve falso.
// Pre: la lista y el iterador fueron creados.
// Post: el iterador avanzó a la posición siguiente de ser posible.
bool lista_iter_avanzar (lista_iter_t *iter) {
	if (iter->actual == NULL) return false;
	iter->anterior = iter->actual;
	iter->actual = iter->actual->siguiente;
	return true;
}

// Da el valor de la posición donde se encuentra el iterador. Si dicha
// posición está vacía, devuelve NULL.
// Pre: la lista y el iterador fueron creados.
// Post: se devolvió el valor de la posición donde se encuentra el iterador
// o NULL, según corresponda.
void* lista_iter_ver_actual (const lista_iter_t *iter) {
	if (iter->actual == NULL) return NULL;
	return iter->actual->dato;
}

// Devuelve verdadero si el iterador se encuentra al final de la lista,
// falso en caso contrario.
// Pre: la lista y el iterador fueron creados.
bool lista_iter_al_final (const lista_iter_t *iter) {
	return (iter->actual == NULL);
}

// Destruye el iterador.
// Pre: el iterador fue creado.
// Post: se destruyó el iterador.
void lista_iter_destruir (lista_iter_t *iter) {
	free (iter);
}


/* ******************************************************************
 *      IMPLEMENTACION DE PRIMITIVAS DE LA LISTA Y EL ITERADOR      *
 * *****************************************************************/

// Se agrega 'dato' a la lista en la posición en la que se encuentra el
// iterador. Devuelve falso en caso de error.
// Pre: la lista y el iterador fueron creados.
// Post: se insertó 'dato' a la lista de ser posible.
bool lista_insertar (lista_t *lista, lista_iter_t *iter, void *dato) {
	//Creo el nodo nuevo.
	nodo_t *nuevo = (nodo_t*) malloc(sizeof(nodo_t));
	if (nuevo == NULL) return false;
	//Le asigno el valor a dato.
	nuevo->dato = dato;
	nuevo->siguiente = iter->actual;
	//Actualizo los punteros de la lista.
	if (lista->primero == iter->actual) lista->primero = nuevo;
	if (lista->ultimo == iter->anterior) lista->ultimo = nuevo;
	//Actualizo el iterador.
	if (iter->anterior != NULL) iter->anterior->siguiente = nuevo;
	iter->actual = nuevo;
	//Actualizo la cantidad de la lista.
	lista->cantidad++;
	return true;
}

// Saca de la lista el elemento que corresponda con la posición en la
// que se encuentre el iterador y lo devuelve. Devuelve NULL si la posición
// no contiene elemento alguno.
// Pre: la lista y el iterador fueron creados.
// Post: se devolvió el elemento o NULL según corresponda.
void* lista_borrar (lista_t *lista, lista_iter_t *iter) {
	if (iter->actual == NULL) return NULL;
	//Creo el nodo auxiliar.
	nodo_t *aux = iter->actual;
	//Actualizo los punteros de la lista.
	if (lista->primero == iter->actual) lista->primero = aux->siguiente;
	if (lista->ultimo == iter->actual) lista->ultimo = iter->anterior;
	//Actualizo el iterador.
	iter->actual = aux->siguiente;
	if (iter->anterior != NULL) iter->anterior->siguiente = iter->actual;
	//Actualizo la cantidad de la lista.
	lista->cantidad--;
	//Extraigo el dato y libero el nodo aux.
	void *dat = aux->dato;
	free (aux);
	return dat;
}
